<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <style>
    .node {
      stroke: #fff;
      stroke-width: 1.5px;
    }

    .link {
      fill:none;
      stroke: #000;
      stroke-opacity: .6;
      opacity: .6
    }

    .directed {
      fill:none;
      stroke: #000;
      stroke-opacity: .6;
      opacity: .6
    }
    marker {
      fill:#bbb;
    }
    #menu-container { position: absolute; bottom: 30px; right: 40px; cursor: default; }

    #menu-message { position: absolute; bottom: 0px; right: 0px; white-space: nowrap;
                    display: none; background-color: #F5F5F5; padding: 10px; }

    #menu-content { position: absolute; bottom: 0px; right: 0px;
                    display: none; background-color: #F5F5F5; border-bottom: 1px solid black;
                    border-right: 1px solid black; border-left: 1px solid black; }

    #menu-content div { border-top: 1px solid black; padding: 10px; white-space: nowrap; }

    #menu-content div:hover { background-color: #FEFEFE;; }

  </style>
<!-- This is where the d3.js script will appear : -->
// D3JS_SCRIPT_HEREEEEEEEEEEE
<script type="text/javascript">
window.onload = function(){
  var pos;

  // Loads the graph data
  var mydiv = document.getElementById("mygraph")
  var graph_as_string = mydiv.innerHTML
  var graph = eval('('+graph_as_string+')');

  var width = document.documentElement.clientWidth-32;
  var height = document.documentElement.clientHeight-32;

  // List of colors
  var color = d3.scale.category10();

  var force = d3.layout.force()
    .charge(graph.charge)
    .linkDistance(graph.link_distance)
    .linkStrength(graph.link_strength)
    .gravity(graph.gravity)
    .size([width, height])
    .links(graph.links)
    .nodes(graph.nodes);

  // Adapts the graph layout to the javascript window's dimensions
  if(graph.pos.length != 0){
    center_and_scale(graph);
  }

  // SVG window
  var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("pointer-events", "all") // Zoom+move management
    .append('svg:g')
    .call(d3.behavior.zoom().on("zoom", redraw_on_zoom))
    .append('svg:g');

  // Zooming
  svg.append('svg:rect')
    .attr('x',      -10000)
    .attr('y',      -10000)
    .attr('width', 2*10000)
    .attr('height',2*10000)
    .attr('fill', 'white');

  var drag_in_progress = false;
  function redraw_on_zoom() {
      if(!drag_in_progress){
	  svg.attr("transform",
		   "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
      }
  }

  // Edges
  var link = svg.selectAll(".link")
    .data(force.links())
    .enter().append("path")
    .attr("class", function(d) { return "link directed"; })
    .attr("marker-end", function(d) { return "url(#directed)"; })
    .style("stroke",function(d) { return d.color; })
    .style("stroke-width", graph.edge_thickness+"px");

  // Loops
  var loops = svg.selectAll(".loop")
  .data(graph.loops)
  .enter().append("circle")
  .attr("class", "link")
  .attr("r", function(d) { return d.curve; })
  .style("stroke",function(d) { return d.color; })
  .style("stroke-width", graph.edge_thickness+"px");

  // Nodes
  var node = svg.selectAll(".node")
  .data(force.nodes())
  .enter().append("circle")
  .attr("class", "node")
  .attr("r", graph.vertex_size)
  .style("fill", function(d) { return color(d.group); })
  .call(force.drag()
	.on('dragstart', function (){ drag_in_progress=true; })
	.on('dragend'  , function (){ drag_in_progress=false;}))

  node.append("title").text(function(d) { return d.name; });

  // Vertex labels
  if(graph.vertex_labels){
    var v_labels = svg.selectAll(".v_label")
    .data(force.nodes())
    .enter()
    .append("svg:text")
    .attr("vertical-align", "middle")
    .text(function(d) { return d.name; })
  }
  // Edge labels
  if(graph.edge_labels){
    var e_labels = svg.selectAll(".e_label")
    .data(force.links())
    .enter()
    .append("svg:text")
    .attr("text-anchor", "middle")
    .text(function(d) { return d.name; })

    var l_labels = svg.selectAll(".l_label")
    .data(graph.loops)
    .enter()
    .append("svg:text")
    .attr("text-anchor", "middle")
    .text(function(d,i) { return graph.loops[i].name; })
  }

  // Arrows, for directed graphs
  if(graph.directed){
    svg.append("svg:defs").selectAll("marker")
    .data(["directed"])
    .enter().append("svg:marker")
    .attr("id", String)
    // viewbox is a rectangle with bottom-left corder (0,-2), width 4 and height 4
    .attr("viewBox", "0 -2 4 4")
    // This formula took some time ... :-P
    .attr("refX", Math.ceil(2*Math.sqrt(graph.vertex_size)))
    .attr("refY", 0)
    .attr("markerWidth", 4)
    .attr("markerHeight", 4)
    .attr("preserveAspectRatio",false)
    .attr("orient", "auto")
    .append("svg:path")
    // triangles with endpoints (0,-2), (4,0), (0,2)
    .attr("d", "M0,-2L4,0L0,2");
  }

  // The function 'line' takes as input a sequence of tuples, and returns a
  // curve interpolating these points.
  var line = d3.svg.line()
  .interpolate("cardinal")
  .tension(.2)
  .x(function(d) {return d.x;})
  .y(function(d) {return d.y;})

  /////////////////////////////////////////////
  // This is where all movements are defined //
  /////////////////////////////////////////////
  force.on("tick", function() {

    // Position of vertices
    node.attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });

    // Position of edges
    link.attr("d", function(d) {

      // Straight edges
      if(d.curve == 0){
        return "M" + d.source.x + "," + d.source.y + " L" + d.target.x + "," + d.target.y;
      }
      // Curved edges
      else {
        var p = third_point_of_curved_edge(d.source,d.target,d.curve)
        return line([{'x':d.source.x,'y':d.source.y},
        {'x':p[0],'y':p[1]},
        {'x':d.target.x,'y':d.target.y}])
      }
    });

    // Position of Loops
    if(graph.loops.length!=0){
      loops
      .attr("cx",function(d) { return force.nodes()[d.source].x; })
      .attr("cy",function(d) { return force.nodes()[d.source].y-d.curve; })
    }

    // Position of vertex labels
    if(graph.vertex_labels){
      v_labels
      .attr("x",function(d) { return d.x+graph.vertex_size; })
      .attr("y",function(d) { return d.y; })
    }
    // Position of the edge labels
    if(graph.edge_labels){
      e_labels
      .attr("x",function(d) { return third_point_of_curved_edge(d.source,d.target,d.curve+3)[0]; })
      .attr("y",function(d) { return third_point_of_curved_edge(d.source,d.target,d.curve+3)[1]; })
      l_labels
      .attr("x",function(d,i) { return force.nodes()[d.source].x; })
      .attr("y",function(d,i) { return force.nodes()[d.source].y-2*d.curve-1; })
    }
  });

    // Returns the coordinates of a point located at distance d from the
    // barycenter of two points pa, pb.
    function third_point_of_curved_edge(pa,pb,d){
	var ox=pa.x,oy=pa.y,dx=pb.x,dy=pb.y;
	var cx=(dx+ox)/2,cy=(dy+oy)/2;
	var ny=-(dx-ox),nx=dy-oy;
	var nn = Math.sqrt(nx*nx+ny*ny)
	return [cx+d*nx/nn,cy+d*ny/nn]
    }

    // Applies a homothety to the points of the graph respecting the
    // aspect ratio, so that the graph takes the whole javascript
    // window and is centered
    function center_and_scale(graph){
	var minx = graph.pos[0][0];
	var maxx = graph.pos[0][0];
	var miny = graph.pos[0][1];
	var maxy = graph.pos[0][1];

	graph.nodes.forEach(function(d, i) {
	    maxx = Math.max(maxx, graph.pos[i][0]);
	    minx = Math.min(minx, graph.pos[i][0]);
	    maxy = Math.max(maxy, graph.pos[i][1]);
	    miny = Math.min(miny, graph.pos[i][1]);
	});

	var border = 60
	var xspan = maxx - minx;
	var yspan = maxy - miny;

	var scale = Math.min((height-border)/yspan, (width-border)/xspan);
	var xshift = (width-scale*xspan)/2
	var yshift = (height-scale*yspan)/2

	force.nodes().forEach(function(d, i) {
	    d.x = scale*(graph.pos[i][0] - minx) + xshift;
	    d.y = scale*(graph.pos[i][1] - miny) + yshift;
	});
    }

    // Starts the automatic force layout
    force.start();
    if(graph.pos.length != 0){
	force.tick();
	force.stop();
	graph.nodes.forEach(function(d, i) {
	    d.fixed=true;
	});

    }

}
  // menu functions

    function toggleMenu() {

        var m = document.getElementById( 'menu-content' );
        if ( m.style.display === 'block' ) m.style.display = 'none'
        else m.style.display = 'block';

    }

    function saveAsSVG() {

        var doctype = '<?xml version="1.0" standalone="no"?>'
            + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

        var our_style='<style>\
    .node {stroke: #fff;stroke-width: 1.5px;}\
    .link {fill:none;stroke: #000;stroke-opacity: .6;opacity: .6}\
    .directed {fill:none;stroke: #000;stroke-opacity: .6;opacity: .6}\
    marker {fill:#bbb;}</style>';

        // serialize our SVG XML to a string.
        var source = (new XMLSerializer()).serializeToString(d3.select('svg').node());

        var styled_source = source.replace('"all"><g>', '"all">' + our_style + "<g>");
        // create a file blob of our SVG.
        var blob = new Blob([doctype + styled_source], { type: 'image/svg+xml;charset=utf-8' });
        var a = document.body.appendChild( document.createElement( 'a' ) );
        a.href = window.URL.createObjectURL( blob );
        a.download = 'my_graph.svg';
        a.click()
    }

</script>

</head>
<body>
<div id="mygraph" style="display:none">
<!-- This is where the graph data generated by Sage will appear : -->
// GRAPH_DATA_HEREEEEEEEEEEE
</div>
<div id="menu-container" onclick="toggleMenu()">&#x24d8;
<div id="menu-message"></div>
<div id="menu-content">
<div onclick="saveAsSVG()">Save as SVG</div>
<div>Close Menu</div>
</div></div>
</body>
</html>
